﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using EffiProz.Core.Errors;

namespace EffiProz.Core.DataTypes
{
    public class NumberType : SqlType
    {

        public const int tinyintPrecision = 3;
        public const int smallintPrecision = 5;
        public const int integerPrecision = 10;
        public const int bigintPrecision = 19;
        public const int doublePrecision = 16;
        public const int defaultNumericPrecision = 29;
        public const int defaultNumericScale = 10;
        public const int maxNumericPrecision = int.MaxValue;
        public const int bigintSquareNumericPrecision = 40;

        //
        const int TINYINT_WIDTH = 8;
        const int SMALLINT_WIDTH = 16;
        const int INTEGER_WIDTH = 32;
        const int BIGINT_WIDTH = 64;
        const int DOUBLE_WIDTH = 64;    // nominal width
        const int DECIMAL_WIDTH = 128;    // nominal width

        //
        public static SqlType SQL_NUMERIC_DEFAULT_INT =
            new NumberType(Types.NUMERIC, defaultNumericPrecision, 0);

        //
        public static decimal MAX_LONG = long.MaxValue;
        public static decimal MIN_LONG = long.MinValue;
        public static decimal MAX_INT = int.MaxValue;
        public static decimal MIN_INT = int.MinValue;


        //
        public int typeWidth;

        public NumberType(int type, long precision, int scale)
            : base(Types.SQL_NUMERIC, type, precision, scale)
        {

            switch (type)
            {

                case Types.TINYINT:
                    typeWidth = TINYINT_WIDTH;
                    break;

                case Types.SQL_SMALLINT:
                    typeWidth = SMALLINT_WIDTH;
                    break;

                case Types.SQL_INTEGER:
                    typeWidth = INTEGER_WIDTH;
                    break;

                case Types.SQL_BIGINT:
                    typeWidth = BIGINT_WIDTH;
                    break;

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    typeWidth = DOUBLE_WIDTH;
                    break;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    typeWidth = DECIMAL_WIDTH;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        /**
         * Returns decimal precision for NUMERIC/DECIMAL. Returns binary precision
         * for other parts.
         */
        public int getPrecision()
        {

            switch (typeCode)
            {

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                case Types.SQL_BIGINT:
                    return typeWidth;

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return 64;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return (int)precision;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override int displaySize()
        {

            switch (typeCode)
            {

                case Types.SQL_DECIMAL:
                case Types.SQL_NUMERIC:
                    if (scale == 0)
                    {
                        if (precision == 0)
                        {
                            return 646456995;    // precision + "-.".length()}
                        }

                        return (int)precision + 1;
                    }

                    if (precision == scale)
                    {
                        return (int)precision + 3;
                    }

                    return (int)precision + 2;

                case Types.SQL_FLOAT:
                case Types.SQL_REAL:
                case Types.SQL_DOUBLE:
                    return 23;                   // String.valueOf(-Double.MaxValue).length();

                case Types.SQL_BIGINT:
                    return 20;                   // decimal precision + "-".length();

                case Types.SQL_INTEGER:
                    return 11;                   // decimal precision + "-".length();

                case Types.SQL_SMALLINT:
                    return 6;                    // decimal precision + "-".length();

                case Types.TINYINT:
                    return 4;                    // decimal precision + "-".length();

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override int getAdoTypeCode()
        {

            switch (typeCode)
            {
                case Types.TINYINT:
                    return (int)DbType.Byte;
                case Types.SQL_SMALLINT:
                    return (int)DbType.Int16;
                case Types.SQL_INTEGER:
                    return (int)DbType.Int32;
                case Types.SQL_BIGINT:
                    return (int)DbType.Int64;

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return (int)DbType.Double;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return (int)DbType.Decimal;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override String getCSharpClassName()
        {

            switch (typeCode)
            {

                case Types.TINYINT:
                    return "System.Byte";
                case Types.SQL_SMALLINT:
                    return "System.Int16";
                case Types.SQL_INTEGER:
                    return "System.Int32";
                case Types.SQL_BIGINT:
                    return "System.Int64";

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return "System.Double";

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return "System.Decimal";

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override String getNameString()
        {

            switch (typeCode)
            {

                case Types.TINYINT:
                    return Tokens.T_TINYINT;

                case Types.SQL_SMALLINT:
                    return Tokens.T_SMALLINT;

                case Types.SQL_INTEGER:
                    return Tokens.T_INTEGER;

                case Types.SQL_BIGINT:
                    return Tokens.T_BIGINT;

                case Types.SQL_REAL:
                    return Tokens.T_REAL;

                case Types.SQL_FLOAT:
                    return Tokens.T_FLOAT;

                case Types.SQL_DOUBLE:
                    return Tokens.T_DOUBLE;

                case Types.SQL_NUMERIC:
                    return Tokens.T_NUMERIC;

                case Types.SQL_DECIMAL:
                    return Tokens.T_DECIMAL;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override String getFullNameString()
        {

            switch (typeCode)
            {

                case Types.SQL_DOUBLE:
                    return "DOUBLE PRECISION";

                default:
                    return getNameString();
            }
        }

        public override String getDefinition()
        {

            switch (typeCode)
            {

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    StringBuilder sb = new StringBuilder(16);

                    sb.Append(getNameString());
                    sb.Append('(');
                    sb.Append(precision);

                    if (scale != 0)
                    {
                        sb.Append(',');
                        sb.Append(scale);
                    }

                    sb.Append(')');

                    return sb.ToString();

                default:
                    return getNameString();
            }
        }

        public override long getMaxPrecision()
        {

            switch (typeCode)
            {

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return maxNumericPrecision;

                default:
                    return getNumericPrecisionInRadix();
            }
        }

        public override int getMaxScale()
        {

            switch (typeCode)
            {

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return Int16.MaxValue;

                default:
                    return 0;
            }
        }

        public override bool acceptsPrecision()
        {

            switch (typeCode)
            {

                case Types.SQL_DECIMAL:
                case Types.SQL_NUMERIC:
                case Types.SQL_FLOAT:
                    return true;

                default:
                    return false;
            }
        }

        public override bool acceptsScale()
        {

            switch (typeCode)
            {

                case Types.SQL_DECIMAL:
                case Types.SQL_NUMERIC:
                    return true;

                default:
                    return false;
            }
        }

        public override int getPrecisionRadix()
        {

            if (typeCode == Types.SQL_DECIMAL || typeCode == Types.SQL_NUMERIC)
            {
                return 10;
            }

            return 2;
        }

        public override bool isNumberType()
        {
            return true;
        }

        public override bool isIntegralType()
        {

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return false;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return scale == 0;

                default:
                    return true;
            }
        }

        public override bool isExactNumberType()
        {

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return false;

                default:
                    return true;
            }
        }

        public override int precedenceDegree(SqlType other)
        {

            if (other.isNumberType())
            {
                int otherWidth = ((NumberType)other).typeWidth;

                return otherWidth - typeWidth;
            }

            return int.MinValue;
        }

        public override SqlType getAggregateType(SqlType other)
        {

            if (this == other)
            {
                return this;
            }

            if (other.isCharacterType())
            {
                return other.getAggregateType(this);
            }

            switch (other.typeCode)
            {

                case Types.SQL_ALL_TYPES:
                    return this;

                case Types.BOOLEAN:
                    return this;

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                case Types.SQL_BIGINT:
                    break;

                default:
                    throw Error.error(ErrorCode.X_42562);
            }

            if (typeWidth == DOUBLE_WIDTH)
            {
                return this;
            }

            if (((NumberType)other).typeWidth == DOUBLE_WIDTH)
            {
                return other;
            }

            if (typeWidth <= BIGINT_WIDTH
                    && ((NumberType)other).typeWidth <= BIGINT_WIDTH)
            {
                return (typeWidth > ((NumberType)other).typeWidth) ? this
                                                                    : other;
            }

            int newScale = scale > other.scale ? scale
                                               : other.scale;
            long newDigits = precision - scale > other.precision - other.scale
                             ? precision - scale
                             : other.precision - other.scale;

            return getNumberType(Types.SQL_DECIMAL, newDigits + newScale,
                                 newScale);
        }

        /**
         *  Returns a SQL type "wide" enough to represent the result of the
         *  expression.<br>
         *  A type is "wider" than the other if it can represent all its
         *  numeric values.<BR>
         *  Arithmetic operation terms are promoted to a type that can
         *  represent the resulting values and avoid incorrect results.<p>
         *  FLOAT/REAL/DOUBLE used in an operation results in the same type,
         *  regardless of the type of the other operand.
         *  When the result or the expression is converted to the
         *  type of the target column for storage, an exception is thrown if the
         *  resulting value cannot be stored in the column<p>
         *  Types narrower than INTEGER (int) are promoted to
         *  INTEGER. The order of promotion is as follows<p>
         *
         *  INTEGER, BIGINT, NUMERIC/DECIMAL<p>
         *
         *  TINYINT and SMALLINT in any combination return INTEGER<br>
         *  TINYINT/SMALLINT/INTEGER and INTEGER return BIGINT<br>
         *  TINYINT/SMALLINT/INTEGER and BIGINT return NUMERIC/DECIMAL<br>
         *  BIGINT and BIGINT return NUMERIC/DECIMAL<br>
         *  REAL/FLOAT/DOUBLE and any type return REAL/FLOAT/DOUBLE<br>
         *  NUMERIC/DECIMAL any type other than REAL/FLOAT/DOUBLE returns NUMERIC/DECIMAL<br>
         *  In the case of NUMERIC/DECIMAL returned, the result precision is always
         *  large enough to express any value result, while the scale depends on the
         *  operation:<br>
         *  For ADD/SUBTRACT/DIVIDE, the scale is the larger of the two<br>
         *  For MULTIPLY, the scale is the sum of the two scales<br>
         */
        public override SqlType getCombinedType(SqlType other, int operation)
        {

            if (other.typeCode == Types.SQL_ALL_TYPES)
            {
                other = this;
            }

            switch (operation)
            {

                case OpTypes.ADD:
                    break;

                case OpTypes.MULTIPLY:
                    if (other.isIntervalType())
                    {
                        return other.getCombinedType(this, OpTypes.MULTIPLY);
                    }
                    break;

                case OpTypes.DIVIDE:
                case OpTypes.SUBTRACT:
                default:

                    // all derivatives of equality ops or comparison ops
                    return getAggregateType(other);
            }

            // resolution for ADD and MULTIPLY only
            if (!other.isNumberType())
            {
                throw Error.error(ErrorCode.X_42562);
            }

            if (typeWidth == DOUBLE_WIDTH
                    || ((NumberType)other).typeWidth == DOUBLE_WIDTH)
            {
                return SqlType.SQL_DOUBLE;
            }

            int sum = typeWidth + ((NumberType)other).typeWidth;

            if (sum <= INTEGER_WIDTH)
            {
                return SqlType.SQL_INTEGER;
            }

            if (sum <= BIGINT_WIDTH)
            {
                return SqlType.SQL_BIGINT;
            }

            int newScale;
            long newDigits;

            switch (operation)
            {

                //            case OpCodes.DIVIDE :
                //            case OpCodes.SUBTRACT :
                case OpTypes.ADD:
                    newScale = scale > other.scale ? scale
                                                   : other.scale;
                    newDigits = precision - scale > other.precision - other.scale
                                ? precision - scale
                                : other.precision - other.scale;

                    newDigits++;
                    break;

                case OpTypes.MULTIPLY:
                    newDigits = precision - scale + other.precision - other.scale;
                    newScale = scale + other.scale;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }

            return getNumberType(Types.SQL_DECIMAL, newScale + newDigits,
                                 newScale);
        }

        public override int compare(Session session, Object a, Object b)
        {

            if (a == b)
            {
                return 0;
            }

            if (a == null)
            {
                return -1;
            }

            if (b == null)
            {
                return 1;
            }

            switch (typeCode)
            {

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    {
                        if (b is int)
                        {
                            int ai = Convert.ToInt32(a);
                            int bi = Convert.ToInt32(b);

                            return (ai > bi) ? 1
                                             : (bi > ai ? -1
                                                        : 0);
                        }
                        else if (b is Double)
                        {
                            double ai = Convert.ToDouble(a);
                            double bi = Convert.ToDouble(a);

                            return (ai > bi) ? 1
                                             : (bi > ai ? -1
                                                        : 0);
                        }
                        else if (b is decimal)
                        {
                            decimal ad = Convert.ToDecimal(a);
                            int i = ad.CompareTo((decimal)b);

                            return (i == 0) ? 0
                                            : (i < 0 ? -1
                                                     : 1);
                        }
                    }

                    goto case Types.SQL_BIGINT;
                // fall through
                case Types.SQL_BIGINT:
                    {
                        if (b is long)
                        {
                            long longa = Convert.ToInt64(a);
                            long longb = Convert.ToInt64(b);

                            return (longa > longb) ? 1
                                                   : (longb > longa ? -1
                                                                    : 0);
                        }
                        else if (b is Double)
                        {
                            Decimal ad = Convert.ToDecimal(a);
                            Decimal bd = Convert.ToDecimal(b);
                            int i = ad.CompareTo(bd);

                            return (i == 0) ? 0
                                            : (i < 0 ? -1
                                                     : 1);
                        }
                        else if (b is Decimal)
                        {
                            Decimal ad = Convert.ToDecimal(a);
                            int i = ad.CompareTo((Decimal)b);

                            return (i == 0) ? 0
                                            : (i < 0 ? -1
                                                     : 1);
                        }
                    }
                    goto case Types.SQL_DOUBLE;
                // fall through
                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {

                        /** @todo big-decimal etc */
                        double ad = Convert.ToDouble(a);
                        double bd = Convert.ToDouble(b);

                        return (ad > bd) ? 1
                                         : (bd > ad ? -1
                                                    : 0);
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        Decimal bd = convertToDecimal(b);
                        int i = ((Decimal)a).CompareTo(bd);

                        return (i == 0) ? 0
                                        : (i < 0 ? -1
                                                 : 1);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        /** @todo - review usage to see if range enforcement / java type conversion is necessary */
        public override Object convertToTypeLimits(SessionInterface session, Object a)
        {

            if (a == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                case Types.SQL_BIGINT:
                    return a;

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return a;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    Decimal dec = Convert.ToDecimal(a);

                    dec = Math.Round(dec, scale, MidpointRounding.AwayFromZero);

                    decimal big = (dec * (long)Math.Pow(10, scale));
                    int sign = big < 0 ? 1 : 0;

                    if (big.ToString("F0").Length - sign > precision)
                    {
                        throw Error.error(ErrorCode.X_22003);
                    }

                    return dec;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override Object convertToType(SessionInterface session, Object a,
                                    SqlType otherType)
        {

            if (a == null)
            {
                return a;
            }

            if (otherType.typeCode == typeCode)
            {
                switch (typeCode)
                {

                    case Types.SQL_NUMERIC:
                    case Types.SQL_DECIMAL:
                        if (otherType.scale == scale
                                && otherType.precision <= precision)
                        {
                            return a;
                        }
                        break;

                    default:
                        return a;
                }
            }

            if (otherType.isIntervalType())
            {
                int endType = ((IntervalType)otherType).endIntervalType;

                switch (endType)
                {

                    case Types.SQL_INTERVAL_YEAR:
                    case Types.SQL_INTERVAL_MONTH:
                    case Types.SQL_INTERVAL_DAY:
                    case Types.SQL_INTERVAL_HOUR:
                    case Types.SQL_INTERVAL_MINUTE:
                        {
                            long value = ((IntervalType)otherType).convertToLong(a);

                            return convertToType(session, value, SqlType.SQL_BIGINT);
                        }
                    case Types.SQL_INTERVAL_SECOND:
                        {
                            long seconds = ((IntervalSecondData)a).units;
                            long nanos = ((IntervalSecondData)a).nanos;
                            Decimal value =
                                ((DTIType)otherType).getSecondPart(seconds, nanos);

                            return value;
                        }
                }
            }

            switch (otherType.typeCode)
            {

                case Types.SQL_CLOB:
                    a = ((ClobData)a).getSubString(
                        session, 0L, (int)((ClobData)a).length(session));
                    goto case Types.VARCHAR_IGNORECASE;
                // fall through
                case Types.SQL_CHAR:
                case Types.SQL_VARCHAR:
                case Types.VARCHAR_IGNORECASE:
                    {
                        a = session.getScanner().convertToNumber((String)a, this);

                        return convertToDefaultType(session, a);
                    }
                case Types.BOOLEAN:
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                case Types.SQL_BIGINT:
                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    break;

                case Types.SQL_BIT:
                case Types.SQL_BIT_VARYING:
                    if (otherType.precision == 1)
                    {
                        if (((BinaryData)a).getBytes()[0] == 0)
                        {
                            a = 0;
                        }
                        else
                        {
                            a = 1;
                        }

                        break;
                    }
                    break;
                default:
                    throw Error.error(ErrorCode.X_42561);
            }

            switch (this.typeCode)
            {

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    return convertToInt(a, this.typeCode);

                case Types.SQL_BIGINT:
                    return convertToLong(a);

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return convertToDouble(a);

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    Decimal value = convertToDecimal(a);

                    return convertToTypeLimits(session, value);

                default:
                    throw Error.error(ErrorCode.X_42561);
            }
        }

        public override Object convertToTypeAdo(SessionInterface session, Object a,
                                        SqlType otherType)
        {

            if (a == null)
            {
                return a;
            }

            switch (otherType.typeCode)
            {

                case Types.SQL_BOOLEAN:
                    a = Convert.ToBoolean(a) ? 1 : 0;
                    otherType = SqlType.SQL_INTEGER;
                    break;
            }

            return convertToType(session, a, otherType);
        }

        public override Object convertSQLToCSharp(SessionInterface session, Object a)
        {
            switch (typeCode)
            {
                case Types.TINYINT:
                    return Convert.ToByte(a);
                 
                case Types.SQL_SMALLINT:
                    return Convert.ToInt16(a);
                  
                case Types.SQL_INTEGER:
                    return Convert.ToInt32(a);
                  
                case Types.SQL_BIGINT:
                    return Convert.ToInt64(a);
                   
                case Types.SQL_FLOAT:
                    return Convert.ToSingle(a);
                 
                case Types.SQL_DOUBLE:
                case Types.SQL_REAL:
                    return Convert.ToDouble(a);
                  
                case Types.NUMERIC:
                case Types.SQL_DECIMAL:
                    return Convert.ToDecimal(a);
                  
            }

            return a;
        }

        /**
         * Converts a value to this type
         */
        public override Object convertToDefaultType(SessionInterface session, Object a)
        {

            if (a == null)
            {
                return a;
            }

            SqlType otherType;

            if (a is long || a is short || a is byte || a is int || a is Single || a is double || a is decimal)
            {
                if (a is Single)
                {
                    a = (Double)((Single)a);
                }
                else if (a is Byte)
                {
                    a = (int)(byte)a;
                }
                else if (a is short)
                {
                    a = (int)(short)a;
                }

                if (a is int)
                {
                    otherType = SqlType.SQL_INTEGER;
                }
                else if (a is long)
                {
                    otherType = SqlType.SQL_BIGINT;
                }
                else if (a is Double)
                {
                    otherType = SqlType.SQL_DOUBLE;
                }
                else if (a is Decimal)
                {
                    if (typeCode == Types.SQL_DECIMAL
                            || typeCode == Types.SQL_NUMERIC)
                    {
                        return convertToTypeLimits(session, a);
                    }

                    Decimal val = (Decimal)a;

                    otherType = getNumberType(Types.SQL_DECIMAL,
                                              precision, scale);
                }
                else
                {
                    throw Error.error(ErrorCode.X_42561);
                }
            }
            else if (a is String)
            {
                otherType = SqlType.SQL_VARCHAR;
            }
            else
            {
                throw Error.error(ErrorCode.X_42561);
            }

            return convertToType(session, a, otherType);
        }

        /**
         * Type narrowing from DOUBLE/DECIMAL/NUMERIC to BIGINT / INT / SMALLINT / TINYINT
         * following SQL rules. When conversion is from a non-integral type,
         * digits to the right of the decimal point are lost.
         */

        /**
         * Converter from a numeric object to Integer. Input is checked to be
         * within range represented by the given number type.
         */
        static int convertToInt(Object a, int type)
        {

            int value;

            if (a is bool)
            {
                value = ((bool)a) ? 1 : 0;
            }
            else if (a is int)
            {
                if (type == Types.SQL_INTEGER)
                {
                    return (int)a;
                }

                value = (int)a;
            }
            else if (a is long)
            {
                long temp = ((long)a);

                if (int.MaxValue < temp || temp < int.MinValue)
                {
                    throw Error.error(ErrorCode.X_22003);
                }

                value = (int)temp;
            }
            else if (a is Decimal)
            {
                Decimal bd = ((Decimal)a);

                if (bd.CompareTo(MAX_INT) > 0 || bd.CompareTo(MIN_INT) < 0)
                {
                    throw Error.error(ErrorCode.X_22003);
                }

                value = (int)bd;
            }
            else if (a is Double || a is Single)
            {
                double d = Convert.ToDouble(a);

                if (Double.IsInfinity(d) || Double.IsNaN(d)
                        || d >= (double)int.MaxValue + 1
                        || d <= (double)int.MinValue - 1)
                {
                    throw Error.error(ErrorCode.X_22003);
                }

                value = (int)d;
            }
            else
            {
                throw Error.error(ErrorCode.X_42561);
            }

            if (type == Types.TINYINT)
            {
                if (Byte.MaxValue < value || value < Byte.MinValue)
                {
                    throw Error.error(ErrorCode.X_22003);
                }
            }
            else if (type == Types.SQL_SMALLINT)
            {
                if (Int16.MaxValue < value || value < Int16.MinValue)
                {
                    throw Error.error(ErrorCode.X_22003);
                }
            }

            return value;
        }

        /**
         * Converter from a numeric object to Long. Input is checked to be
         * within range represented by Long.
         */
        static long convertToLong(Object a)
        {
            if (a is bool)
            {
                return ((bool)a) ? 1 : 0;
            }
            else if (a is int)
            {
                return (long)(int)a;
            }
            else if (a is long)
            {
                return (long)a;
            }
            else if (a is Decimal)
            {
                Decimal bd = (Decimal)a;

                if (bd.CompareTo(MAX_LONG) > 0 || bd.CompareTo(MIN_LONG) < 0)
                {
                    throw Error.error(ErrorCode.X_22003);
                }

                return (long)bd;
            }
            else if (a is Double || a is Single)
            {
                double d = Convert.ToDouble(a);

                if (Double.IsInfinity(d) || Double.IsNaN(d)
                        || d >= (double)long.MaxValue + 1
                        || d <= (double)long.MinValue - 1)
                {
                    throw Error.error(ErrorCode.X_22003);
                }

                return (long)d;
            }
            else
            {
                throw Error.error(ErrorCode.X_42561);
            }
        }


        public static double toDouble(Object a)
        {
            return convertToDouble(a);
        }

        /**
         * Converter from a numeric object to Double. Input is checked to be
         * within range represented by Double
         */
        private static Double convertToDouble(Object a)
        {

            double value;
            if (a is bool)
            {
                return ((bool)a) ? 1 : 0;
            }
            else if (a is double)
            {
                return (Double)a;
            }
            else if (a is Decimal)
            {
                Decimal bd = (Decimal)a;

                value = (double)bd;

                //int        signum = bd.signum();
                //Decimal bdd    = new Decimal(value + signum);

                //if (bdd.CompareTo(bd) != signum) {
                //    throw Error.error(ErrorCode.X_22003);
                //}
            }
            else
            {
                value = Convert.ToDouble(a);
            }

            return value;
        }


        private static Decimal convertToDecimal(Object a)
        {

            if (a is bool)
            {
                return ((bool)a) ? 1 : 0;
            }
            else if (a is Decimal)
            {
                return (Decimal)a;
            }
            else if (a is int || a is long)
            {
                return Convert.ToDecimal(a);
            }
            else if (a is Double)
            {
                double value = Convert.ToDouble(a);

                if (Double.IsInfinity(value) || Double.IsNaN(value))
                {
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
                }

                return new Decimal(value);
            }
            else
            {
                throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override String convertToString(Object a)
        {

            if (a == null)
            {
                return null;
            }

            switch (this.typeCode)
            {

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                case Types.SQL_BIGINT:
                    return a.ToString();

                case Types.SQL_REAL:
                case Types.SQL_DOUBLE:
                    double value = Convert.ToDouble(a);

                    /** @todo - java 5 format change */
                    if (Double.IsNegativeInfinity(value))
                    {
                        return "-1E0/0";
                    }

                    if (double.IsPositiveInfinity(value))
                    {
                        return "1E0/0";
                    }

                    if (Double.IsNaN(value))
                    {
                        return "0E0/0E0";
                    }

                    String s = value.ToString();

                    // ensure the engine treats the value as a DOUBLE, not DECIMAL
                    if (s.IndexOf('E') < 0)
                    {
                        s = s + "E0";
                    }

                    return s;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return a.ToString();

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override String convertToSQLString(Object a)
        {

            if (a == null)
            {
                return Tokens.T_NULL;
            }

            return convertToString(a);
        }

        public override bool canConvertFrom(SqlType otherType)
        {

            if (otherType.typeCode == Types.SQL_ALL_TYPES)
            {
                return true;
            }

            if (otherType.isNumberType())
            {
                return true;
            }

            if (otherType.isIntervalType())
            {
                return true;
            }

            if (otherType.isCharacterType())
            {
                return true;
            }

            if (otherType.isBitType() && otherType.precision == 1)
            {
                return true;
            }

            return false;
        }

        public int CompareToTypeRange(Object o)
        {

            if (!(o is int || o is long || o is double || o is float || o is short || o is byte || o is decimal))
            {
                return 0;
            }

            if (o is int || o is long)
            {
                long temp = Convert.ToInt64(o);
                int min;
                int max;

                switch (typeCode)
                {

                    case Types.TINYINT:
                        min = Byte.MinValue;
                        max = Byte.MaxValue;
                        break;

                    case Types.SQL_SMALLINT:
                        min = short.MinValue;
                        max = short.MaxValue;
                        break;

                    case Types.SQL_INTEGER:
                        min = int.MinValue;
                        max = int.MaxValue;
                        break;

                    case Types.SQL_BIGINT:
                        return 0;

                    case Types.SQL_DECIMAL:
                    case Types.SQL_NUMERIC:
                    default:
                        return 0;
                }

                if (max < temp)
                {
                    return 1;
                }

                if (temp < min)
                {
                    return -1;
                }

                return 0;
            }

            return 0;
        }

        public override Object add(Object a, Object b, SqlType otherType)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);
                        double bd = Convert.ToDouble(b);

                        return (double)(ad + bd);

                        //                return new Double(ad + bd);
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        a = convertToDefaultType(null, a);
                        b = convertToDefaultType(null, b);

                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        return (decimal)abd + bbd;
                    }
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    {
                        int ai = Convert.ToInt32(a);
                        int bi = Convert.ToInt32(b);

                        return (int)(ai + bi);
                    }
                case Types.SQL_BIGINT:
                    {
                        long longa = Convert.ToInt64(a);
                        long longb = Convert.ToInt64(b);

                        return (long)(longa + longb);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override Object subtract(Object a, Object b, SqlType otherType)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);
                        double bd = Convert.ToDouble(b);

                        return (double)(ad - bd);

                        //                return new Double(ad + bd);
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        a = convertToDefaultType(null, a);
                        b = convertToDefaultType(null, b);

                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        return (decimal)abd - bbd;
                    }
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    {
                        int ai = Convert.ToInt32(a);
                        int bi = Convert.ToInt32(b);

                        return (int)(ai - bi);
                    }
                case Types.SQL_BIGINT:
                    {
                        long longa = Convert.ToInt64(a);
                        long longb = Convert.ToInt64(b);

                        return (long)(longa - longb);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override Object multiply(Object a, Object b)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);
                        double bd = Convert.ToDouble(b);

                        return (double)(ad * bd);

                        //                return new Double(ad + bd);
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        a = convertToDefaultType(null, a);
                        b = convertToDefaultType(null, b);

                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        return (decimal)abd * bbd;
                    }
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    {
                        int ai = Convert.ToInt32(a);
                        int bi = Convert.ToInt32(b);

                        return (int)(ai * bi);
                    }
                case Types.SQL_BIGINT:
                    {
                        long longa = Convert.ToInt64(a);
                        long longb = Convert.ToInt64(b);

                        return (long)(longa * longb);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public override Object divide(Object a, Object b)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);
                        double bd = Convert.ToDouble(b);

                        if (bd == 0)
                        {
                            throw Error.error(ErrorCode.X_22012);
                        }

                        return (double)(ad / bd);

                        //                return new Double(ad + bd);
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        a = convertToDefaultType(null, a);
                        b = convertToDefaultType(null, b);

                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        if (bbd == 0)
                        {
                            throw Error.error(ErrorCode.X_22012);
                        }


                        return (decimal)abd / bbd;
                    }
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    {
                        int ai = Convert.ToInt32(a);
                        int bi = Convert.ToInt32(b);

                        if (bi == 0)
                        {
                            throw Error.error(ErrorCode.X_22012);
                        }

                        return (int)(ai / bi);
                    }
                case Types.SQL_BIGINT:
                    {
                        long longa = Convert.ToInt64(a);
                        long longb = Convert.ToInt64(b);

                        if (longb == 0)
                        {
                            throw Error.error(ErrorCode.X_22012);
                        }

                        return (long)(longa / longb);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }


        }

        public override Object absolute(Object a)
        {
            return isNegative(a) ? negate(a)
                                 : a;
        }

        public override Object negate(Object a)
        {

            if (a == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        return -Convert.ToDouble(a);

                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return -Convert.ToDecimal(a);

                case Types.TINYINT:
                    {
                        int value = Convert.ToByte(a);

                        if (value == Byte.MinValue)
                        {
                            throw Error.error(ErrorCode.X_22003);
                        }

                        return (-value);
                    }
                case Types.SQL_SMALLINT:
                    {
                        int value = Convert.ToInt16(a);

                        if (value == short.MinValue)
                        {
                            throw Error.error(ErrorCode.X_22003);
                        }

                        return (-value);
                    }
                case Types.SQL_INTEGER:
                    {
                        int value = Convert.ToInt32(a);

                        if (value == int.MinValue)
                        {
                            throw Error.error(ErrorCode.X_22003);
                        }

                        return (-value);
                    }
                case Types.SQL_BIGINT:
                    {
                        long value = Convert.ToInt64(a);

                        if (value == long.MinValue)
                        {
                            throw Error.error(ErrorCode.X_22003);
                        }

                        return (-value);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public int getNumericPrecisionInRadix()
        {

            switch (typeCode)
            {

                case Types.TINYINT:
                    return 8;

                case Types.SQL_SMALLINT:
                    return 16;

                case Types.SQL_INTEGER:
                    return 32;

                case Types.SQL_BIGINT:
                    return 64;

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return 64;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return (int)precision;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public SqlType getIntegralType()
        {

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    return SQL_NUMERIC_DEFAULT_INT;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return scale == 0 ? this
                                      : new NumberType(typeCode, precision, 0);

                default:
                    return this;
            }
        }

        public static bool isZero(Object a)
        {

            if (a is Decimal)
            {
                return (decimal)a == decimal.Zero;
            }
            else if (a is Double)
            {
                return ((Double)a) == 0 || Double.IsNaN((Double)a);
            }
            else
            {
                return Convert.ToInt64(a) == 0;
            }
        }

        public bool isNegative(Object a)
        {

            if (a == null)
            {
                return false;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);

                        return ad < 0;
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return ((Decimal)a) < 0;

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    return Convert.ToInt32(a) < 0;

                case Types.SQL_BIGINT:
                    return Convert.ToInt64(a) < 0;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public int compareToZero(Object a)
        {

            if (a == null)
            {
                return 0;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);

                        return ad == 0 ? 0
                                       : ad < 0 ? -1
                                                : 1;
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    decimal dc = Convert.ToDecimal(a);

                    return dc == 0 ? 0
                                   : dc < 0 ? -1
                                            : 1;

                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                    {
                        int ai = Convert.ToInt32(a);

                        return ai == 0 ? 0
                                       : ai < 0 ? -1
                                                : 1;
                    }
                case Types.SQL_BIGINT:
                    {
                        long al = Convert.ToInt64(a);

                        return al == 0 ? 0
                                       : al < 0 ? -1
                                                : 1;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }

        public static long scaledDecimal(Object a, int scale)
        {

            if (a == null)
            {
                return 0;
            }

            if (scale == 0)
            {
                return 0;
            }

            Decimal value = ((Decimal)a);

            //if (value.scale() == 0) {
            //    return 0;
            //}


            value = Math.Round(value, scale, MidpointRounding.ToEven);
            //value = value.setScale(0, Decimal.ROUND_FLOOR);
            //value = ((Decimal) a).subtract(value);

            //return value.movePointRight(scale).longValue();

            return (long) value;
        }

        public static bool isInLongLimits(Decimal result)
        {

            if (NumberType.MIN_LONG.CompareTo(result) > 0
                    || NumberType.MAX_LONG.CompareTo(result) < 0)
            {
                return false;
            }

            return true;
        }

        public static bool isInLongLimits(long result)
        {

            if (long.MaxValue.CompareTo(result) < 0
                    || long.MinValue.CompareTo(result) > 0)
            {
                return false;
            }

            return true;
        }

        public Object ceiling(Object a)
        {

            if (a == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);
                        ad = Math.Ceiling(ad);

                        if (Double.IsInfinity(ad))
                        {
                            throw Error.error(ErrorCode.X_22003);
                        }

                        return ad;
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        Decimal value = ((Decimal)a);
                        value = Math.Ceiling(value);

                        return value;

                        //if (JavaSystem.precision(value) > precision) {
                        //    throw Error.error(ErrorCode.X_22003);
                        //}
                    }

                // fall through
                default:
                    return a;
            }
        }

        public Object floor(Object a)
        {

            if (a == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                    {
                        double value = Convert.ToDouble(a);
                        value = Math.Floor(value);

                        if (Double.IsInfinity(value))
                        {
                            throw Error.error(ErrorCode.X_22003);
                        }

                        return value;
                    }
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        decimal value = Convert.ToDecimal(a);
                        value = Math.Floor(value);

                        //if (JavaSystem.precision(value) > precision) {
                        //    throw Error.error(ErrorCode.X_22003);
                        //}
                        return value;
                    }

                // fall through
                default:
                    return a;
            }
        }

        public Object truncate(Object a, int s)
        {

            if (a == null)
            {
                return null;
            }

            if (s >= scale)
            {
                return a;
            }

            Decimal dec = convertToDecimal(a);

            dec = Math.Round(dec, s, MidpointRounding.ToEven);
            dec = Math.Round(dec, scale, MidpointRounding.ToEven);

            return convertToDefaultType(null, dec);
        }

        public static NumberType getNumberType(int type, long precision,
                                               int scale)
        {

            switch (type)
            {

                case Types.SQL_INTEGER:
                    return SQL_INTEGER;

                case Types.SQL_SMALLINT:
                    return SQL_SMALLINT;

                case Types.SQL_BIGINT:
                    return SQL_BIGINT;

                case Types.TINYINT:
                    return TINYINT;

                case Types.SQL_REAL:
                case Types.SQL_DOUBLE:
                    return SQL_DOUBLE;

                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    return new NumberType(type, precision, scale);

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "NumberType");
            }
        }
    }
}
